Miles Sound System SDK 7.2a

Performing the Compression and Decompression

Discussion

Under the ASI standard, all compression and decompression takes place within the provider's ASI_stream_process function. This function is extremely open-ended in nature, and completely symmetrical with respect to compression and decompression. The application simply calls it with a request for any desired amount of compressed or decompressed data, from bytes to megabytes. ASI_stream_process fulfills as much of the application's request as possible from the provider's own internal buffers, if any data is left over from a previous call. Subsequently, it invokes the application callback function that was originally passed to ASI_stream_open to request that the application send it enough source data to enable it to fulfill the compression or decompression request. In this case, MSSCHTC.CPP's RECV_stream_CB function is called by the ASI_stream_open handler to obtain compressed data from the server, one "frame" at a time, until enough frames of data have arrived to fulfill the application's request:


// ---------------------------------------------------------------------------
// RECV_stream_CB()
// Runs from client receive thread
// ---------------------------------------------------------------------------
S32 CALLBACK RECV_stream_CB(U32 user,
void FAR *dest,
S32 bytes_requested,
S32 offset)
{
//
// A seek offset of 0 occurs only when the stream is opened (initial seek to
// beginning to determine stream characteristics) and when the first frame
// of the stream is read after opening (when actually beginning to stream
// data). Since we are dealing with a non-seekable input stream, we
// must handle this second case by maintaining a separate read and write
// cursor in the frame buffer. The second 0-offset seek is guaranteed
// to occur while the initial frame data is still in the buffer.
//
// Most voice codecs won't perform any seek operations at all -- this
// functionality is here primarily to support MP3 streaming and similar
// operations. A chat server designed strictly for use with the Voxware
// codecs can safely ignore the offset parameter altogether.
//
//
if (offset != -1)
{
RECV_read_cursor = offset;
}
//
// Data will typically be requested one frame at a time by the ASI codec
// (although frame header components may be requested separately, a few words
// at a time). A return value of less than bytes_requested indicates the
// end of the stream has been reached. We don't support fragmented frames,
// so we'll return 0 to the codec if the client disconnects while we're
// polling it for data. Any ASI codec designed for compatibility with
// IP streaming MUST accept 0-byte return values at any stage.
//
S32 needed = bytes_requested;
while (1)
{
//
// Get as much data as possible from frame buffer
//
if (RECV_read_cursor < RECV_write_cursor)
{
S32 n = min(needed,
RECV_write_cursor - RECV_read_cursor);
memcpy(dest,
&RECV_frame[RECV_read_cursor],
n);
dest = ((C8 *) dest) + n;
RECV_read_cursor += n;
needed -= n;
}
//
// If all requested data has been read, return
//
if (needed <= 0)
{
//
// Keep read/write cursors in first half of frame buffer
//
if (RECV_read_cursor >= RECEIVE_FRAME_SIZE)
{
RECV_read_cursor -= RECEIVE_FRAME_SIZE;
RECV_write_cursor -= RECEIVE_FRAME_SIZE;
}
return bytes_requested;
}
//
// We need more data -- read it into the frame buffer
//
S32 result = NET_poll_for_data(&RECV_frame[RECV_write_cursor],
needed);
if (result == -1)
{
//
// Client disconnected, return 0 to abort current frame
//
return 0;
}
if (result == 0)
{
//
// No data available yet
//
Sleep(10);
}
else
{
//
// Advance the write cursor
//
RECV_write_cursor += result;
}
//
// Block until more data comes in or server disconnects
}
}

As the comments suggest, this code is somewhat more robust than it has to be for simple voice communication with the Voxware codecs. Many ASI decompressors, notably the MP3 decoder, can output different PCM data formats depending on the exact format of the compressed source data. In such a case, the application needs to be able to determine the sample format required to play the decoded data immediately after calling the ASI_stream_open function. So a typical ASI codec must examine the first few bytes of the data when opening a stream, in order to report the stream's format to the application. This is the only case in which a "seek" operation must be supported by the codec when dealing with streamed data, so it's a good practice to support valid offset parameters in an ASI decompressor's callback function if you expect to support more general codecs in the future. (Typically the ability to seek 1 or 2 KB backwards in a stream after the first frame is fetched is sufficient for most popular data formats).

Turning uncompressed PCM data from the input API or data file into compressed data suitable for transmission to the server is an almost identical process, thanks to the inherent symmetry of the ASI specification. MSSCHTC.CPP's transmit_ASI_thread_procedure calls the transmit ASI provider as follows:


//
// See if more transmit data is needed
//
if (current_send_offset == TRANSMIT_PACKET_SIZE)
{
//
// Request data from ASI encoder to send to server
//
// This will block in XMIT_stream_CB( ) until enough input
// data is available to satisfy the request
//
S32 amount = XMIT_stream_process(transmit_stream,
send_buffer,
TRANSMIT_PACKET_SIZE);
if (amount != TRANSMIT_PACKET_SIZE)
{
//
// Bad read
//
active = 0;
return 0;
}
current_send_offset = 0;
}

As in the receiver code above, the XMIT_stream_process function calls the application-supplied callback function XMIT_stream_CB to fetch enough data to fulfill the application's request. In this case, XMIT_stream_CB blocks until sufficient data is available from the input API:


// ---------------------------------------------------------------------------
// XMIT_stream_CB( )
// ---------------------------------------------------------------------------
S32 CALLBACK XMIT_stream_CB(U32 user,
void FAR *dest,
S32 bytes_requested,
S32 offset)
{
//
// Block transmission thread in this routine until specified
// number of bytes available from input, or disconnection occurs
//
S32 bytes_sent = 0;
while (bytes_requested > 0)
{
//
// Allow other threads to run
//
Sleep(3);
//
// Exit if transmit thread killed by main thread
//
// Returning 0 here will cause transmit_thread_procedure() to exit
// when its ASI_stream_process( ) call fails
//
if (!threads_active)
{
return 0;
}
//
// Get input buffer to send
//
if ((input_buffer_tail == -1) ||
(input_buffer_tail == input_buffer_head))
{
//
// No input buffers available
//
continue;
}
//
// Transmit data from input buffer at queue tail
//
C8 FAR *src = &input_buffer [input_buffer_tail] [input_read_offset];
S32 src_avail = INPUT_BUFFER_SIZE - input_read_offset;
S32 send_amount = min(bytes_requested,
src_avail);
memcpy(dest,
src,
send_amount);
dest = ((C8 *) dest) + send_amount;
input_read_offset += send_amount;
bytes_requested -= send_amount;
bytes_sent += send_amount;
if (input_read_offset == INPUT_BUFFER_SIZE)
{
//
// Entire buffer has been sent; advance tail pointer
//
if (input_buffer_tail == N_INPUT_BUFFERS-1)
{
input_buffer_tail = 0;
}
else
{
++input_buffer_tail;
}
input_read_offset = 0;
}
}
return bytes_sent;
}

Use of the ASI codec interface in MSSCHTS.CPP is very similar to the above examples in MSSCHTC.CPP.

Next Topic (Integrating the Codecs with your Networking Architecture)

Previous Topic (Accessing the Codecs Directly with the RIB Interface)


Group: Implementing Voice Chat

For technical support, e-mail Miles3@radgametools.com
© Copyright 1991-2007 RAD Game Tools, Inc. All Rights Reserved.